fix(skill): block @greptileai trigger until all comments replied#656
fix(skill): block @greptileai trigger until all comments replied#656carlos-alm merged 5 commits intomainfrom
Conversation
Prevents the review skill from posting @greptileai re-triggers before replying to every inline Greptile comment. Adds a Step 2g verification script that checks for unanswered comments and blocks the trigger if any exist. Updates the Rules section to make reply-before-trigger the explicit invariant.
|
Claude finished @carlos-alm's task —— View job Reviewing PR: fix(skill): block @greptileai trigger until all comments repliedTodo List:
Root Cause AnalysisProblem Being Solved: Prevents review skill from starting new review cycles while old feedback is still unanswered. Addresses workflow issue where @greptileai was triggered prematurely without replying to all existing comments. Why This Approach: Adds mandatory verification script that exits with error code 1 if any Greptile comments lack replies. This enforces the reply-first invariant at the process level rather than relying on human discipline. Risk Assessment: Low risk - documentation/workflow change only, no code modification. Improves review process integrity. Backlog Compliance
Critical ConcernsNone. The verification script is well-structured with proper error handling and clear messaging. Code Quality Assessment
Final Recommendation
The verification script at lines 208-230 effectively prevents premature @greptileai triggers and the Rules update at line 305 clearly communicates the blocking invariant. This will reduce review noise and ensure feedback is properly addressed before new review cycles begin. |
Greptile SummaryThis PR strengthens the Key changes:
Confidence Score: 5/5Safe to merge — all prior thread issues are resolved and the single remaining finding is a P2 edge-case defensive-programming suggestion. All three P1 issues from previous review threads (false-positive root-comment selection, N+1 API calls, pagination integer-comparison failure) are definitively addressed. The only remaining finding is a P2 jq-error edge case that only fires if the GitHub API returns malformed JSON, which is not a normal operating condition. Per confidence guidance, all P2 findings default to 5/5. .claude/skills/review/SKILL.md lines 217-221 — jq error fallback in Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Step 2g: Re-trigger Greptile] --> B["Fetch all PR review comments once\n(gh api --paginate → all_comments)"]
B --> C["Select root Greptile comments\n(in_reply_to_id == null)"]
C --> D{Any Greptile\nroot comments?}
D -- No --> G[✅ Safe to re-trigger]
D -- Yes --> E["For each comment ID:\ncount non-Greptile replies\n(jq -s .[][] | length)"]
E --> F{reply_count\n== 0?}
F -- Yes --> X[Add to unanswered list]
F -- No --> E
X --> H{unanswered\nlist empty?}
H -- No --> I["❌ BLOCKED — print unanswered IDs\nexit 1\nGo back to Step 2e"]
H -- Yes --> G
G --> J{Greptile already\nreacted positively?}
J -- Yes --> K[Skip re-trigger]
J -- No --> L["Post @greptileai\n(re-trigger)"]
Reviews (3): Last reviewed commit: "Merge branch 'main' into fix/review-skil..." | Re-trigger Greptile |
.claude/skills/review/SKILL.md
Outdated
| greptile_comment_ids=$(gh api repos/optave/codegraph/pulls/<number>/comments --paginate \ | ||
| --jq '[.[] | select(.user.login == "greptile-apps[bot]")] | .[].id') |
There was a problem hiding this comment.
Script selects all Greptile comments, including follow-up thread replies
greptile_comment_ids is populated by selecting every comment where user.login == "greptile-apps[bot]". This includes any follow-up replies Greptile might post inside an existing thread (those will have in_reply_to_id pointing to the thread root).
When the loop later checks for human replies via select(.in_reply_to_id == $cid ...), it uses each Greptile comment's own ID as $cid. But GitHub's threading model stores in_reply_to_id as the root comment of the thread — not the ID of an intermediate reply. As a result, any Greptile follow-up reply would never appear as an in_reply_to_id for other comments, and the script would permanently flag it as unanswered, blocking the trigger even when the thread has been fully addressed.
Fix by restricting the selection to root Greptile comments only (those that are not themselves replies):
| greptile_comment_ids=$(gh api repos/optave/codegraph/pulls/<number>/comments --paginate \ | |
| --jq '[.[] | select(.user.login == "greptile-apps[bot]")] | .[].id') | |
| greptile_comment_ids=$(gh api repos/optave/codegraph/pulls/<number>/comments --paginate \ | |
| --jq '[.[] | select(.user.login == "greptile-apps[bot]" and .in_reply_to_id == null)] | .[].id') |
There was a problem hiding this comment.
Fixed — added and .in_reply_to_id == null to the jq filter so only root Greptile comments are checked. Follow-up thread replies are now excluded, preventing false-positive blocks.
| for cid in $greptile_comment_ids; do | ||
| reply_count=$(gh api repos/optave/codegraph/pulls/<number>/comments --paginate \ | ||
| --jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") | ||
| if [ "$reply_count" -eq 0 ]; then | ||
| unanswered+=("$cid") | ||
| fi |
There was a problem hiding this comment.
N+1 API calls — fetch comments once and process in-memory
Each loop iteration issues a full paginated gh api call for all PR comments just to count replies for a single $cid. For a PR with 10 Greptile comments this means 11 round-trips each retrieving the full comment list. Consider fetching all comments once and passing the payload through jq entirely:
all_comments=$(gh api repos/optave/codegraph/pulls/<number>/comments --paginate)
greptile_comment_ids=$(echo "$all_comments" \
| jq -r '[.[] | select(.user.login == "greptile-apps[bot]" and .in_reply_to_id == null)] | .[].id')
unanswered=()
for cid in $greptile_comment_ids; do
reply_count=$(echo "$all_comments" \
| jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length")
if [ "$reply_count" -eq 0 ]; then
unanswered+=("$cid")
fi
doneThere was a problem hiding this comment.
Fixed — refactored to fetch all comments once into all_comments and filter in-memory with jq. Eliminates the N+1 API call pattern.
.claude/skills/review/SKILL.md
Outdated
| reply_count=$(echo "$all_comments" \ | ||
| | jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") |
There was a problem hiding this comment.
reply_count breaks on multi-page responses
gh api --paginate outputs each page as a separate JSON array, not a single merged array. When all_comments contains multiple pages (i.e., more than 30 PR review comments), the jq expression produces one number per page — e.g., 0\n2 — stored as a multi-line string in reply_count. The subsequent [ "$reply_count" -eq 0 ] bash comparison then receives a non-integer string, causing an "integer expression expected" error (exit code 2), which makes the if branch silently skip, treating every Greptile comment as answered. The check passes falsely and the block never fires.
Fix with jq -s to slurp all pages into one array before counting:
| reply_count=$(echo "$all_comments" \ | |
| | jq "[.[] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") | |
| reply_count=$(echo "$all_comments" \ | |
| | jq -s "[.[][] | select(.in_reply_to_id == $cid and .user.login != \"greptile-apps[bot]\")] | length") |
The greptile_comment_ids selection above is fine as-is — jq emits one ID per line there and the for loop splits on newlines correctly. Only reply_count is affected.
There was a problem hiding this comment.
Fixed — added jq -s with .[][] to slurp all paginated pages into a single array before counting replies. This prevents the multi-line integer comparison failure on PRs with >30 review comments. Also reworded the "only exception" language on line 204 so it no longer conflicts with the mandatory all-replies pre-condition.
gh api --paginate emits one JSON array per page. Without -s, jq produces one number per page, causing the integer comparison to fail silently and bypass the blocking check. Using jq -s slurps all pages into a single array. Also rewords the "only exception" language on the Greptile re-trigger paragraph so it no longer conflicts with the mandatory all-replies pre-condition added earlier.
Summary
/reviewStep 2g that checks every Greptile inline comment has at least one reply before allowing@greptileaito be postedTest plan
/reviewon a PR with unanswered Greptile comments — verify the verification script blocks the trigger/reviewon a PR where all Greptile comments have replies — verify it proceeds to trigger normally